﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Data;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  internal class ItemsWrapper : IList, INotifyCollectionChanged, INotifyPropertyChanged
  {
    #region Constructors

    public ItemsWrapper( ListBox listBox )
    {
      if( listBox == null )
        throw new ArgumentNullException( "listBox" );

      m_listBox = listBox;
    }

    #endregion

    #region Internal Properties

    public CollectionViewSource CollectionViewSource
    {
      get
      {
        if( m_listBox.IsAsyncDataSource )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

        if( m_cachedCollectionViewSource == null )
        {
          m_cachedCollectionViewSource = new CollectionViewSource();

          using( m_cachedCollectionViewSource.DeferRefresh() )
          {
            // Do not access the DataSourceWrapper if the ItemsSource is null
            // to avoid reentrancy caused by ListBox.UpdateDataSourceAndDataSourceEnumerator()
            // when initializing the DataSourceEnumeratorWrapper for an Unbound ListBox.
            if( m_listBox.ItemsSource != null )
            {
              DataSourceWrapper dataSourceWrapper = m_listBox.DataSourceWrapper;

              if( dataSourceWrapper != null )
              {
                ListDataSourceProvider listDataSourceProvider = dataSourceWrapper.Provider as ListDataSourceProvider;

                m_cachedCollectionViewSource.Source = ( listDataSourceProvider == null ) ? null : listDataSourceProvider.DataSource;

                foreach( SortDescription sortDescription in dataSourceWrapper.SortDescriptions )
                {
                  m_cachedCollectionViewSource.SortDescriptions.Add( sortDescription );
                }
              }
            }
            else
            {
              // The ListBox is treated as Unbound
              Debug.Assert( m_listBox.ItemsSource == null, "Using UnboundItems on a ListBox that is not Unbound." );

              m_cachedCollectionViewSource.Source = this.UnboundItems;
            }

            // Apply GroupDescriptions defined on the ListBox
            foreach( GroupDescription groupDescription in m_listBox.GroupDescriptions )
            {
              m_cachedCollectionViewSource.GroupDescriptions.Add( groupDescription );
            }
          }
        }

        Debug.Assert( m_cachedCollectionViewSource != null );

        return m_cachedCollectionViewSource;
      }
    }

    public ObservableCollection<object> UnboundItems
    {
      get
      {
        if( m_unboundList == null )
        {
          m_unboundList = new ObservableCollection<object>();
          m_unboundList.CollectionChanged += this.OnUnboundList_CollectionChanged;
        }

        return m_unboundList;
      }
    }

    private void OnUnboundList_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( !m_listBox.IsInitialized )
        return;

      this.RaiseNotifyPropertyChanged( "Count" );
      this.RaiseNotifyCollectionChanged( e );
    }

    #endregion

    #region Internal Methods

    internal void UpdateItemsSource()
    {
      // Will be re-initialized if required when going through the CollectionViewSource property getter.
      m_cachedCollectionViewSource = null;

      this.RaiseNotifyPropertyChanged( "Count" );
      this.RaiseNotifyCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset ) );
    }

    #endregion

    #region Private Fields

    private ObservableCollection<object> m_unboundList;
    private ListBox m_listBox;
    private CollectionViewSource m_cachedCollectionViewSource;

    #endregion

    #region IList Members

    public int Add( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return ( ( IList )this.UnboundItems ).Add( value );
    }

    public void Clear()
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Clear();
    }

    public bool Contains( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return this.UnboundItems.Contains( value );
    }

    public int IndexOf( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      return this.UnboundItems.IndexOf( value );
    }

    public void Insert( int index, object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Insert( index, value );
    }

    public bool IsFixedSize
    {
      get
      {
        return ( m_listBox.ItemsSource != null );
      }
    }

    public bool IsReadOnly
    {
      get
      {
        return ( m_listBox.ItemsSource != null );
      }
    }

    public void Remove( object value )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.Remove( value );
    }

    public void RemoveAt( int index )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      this.UnboundItems.RemoveAt( index );
    }

    public object this[ int index ]
    {
      get
      {
        if( m_listBox.IsAsyncDataSource )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

        if( m_listBox.ItemsSource == null )
        {
          return this.UnboundItems[ index ];
        }
        else
        {
          if( m_listBox.IsAsyncDataSource )
            throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use and it is an asynchronous DataSource. Access and modify elements with ItemsControl.ItemsSource instead." );

          DataPath returnedDataPath = null;

          ICollection<DataPath> fetchedItems;

          DataSourceEnumeratorWrapper enumerator = m_listBox.DataSourceEnumeratorWrapper;
          DataPath oldCurrent = enumerator.Current;

          enumerator.MoveToHome();

          if( enumerator.MoveAndFetch( index, 1, out fetchedItems ) )
          {
            returnedDataPath = fetchedItems.First<DataPath>();
            enumerator.JumpMoveAndFetch( oldCurrent, 0, 1, out fetchedItems );
          }

          return ( returnedDataPath != null )
            ? returnedDataPath.LastChild.Data
            : null;
        }
      }
      set
      {
        if( m_listBox.ItemsSource != null )
          throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

        this.UnboundItems[ index ] = value;
      }
    }

    #endregion

    #region ICollection Members

    public void CopyTo( Array array, int index )
    {
      if( m_listBox.ItemsSource != null )
        throw new InvalidOperationException( "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead." );

      ( ( ICollection )this.UnboundItems ).CopyTo( array, index );
    }

    public int Count
    {
      get
      {
        if( m_listBox.ItemsSource != null )
          return ( int )m_listBox.DataSourceEnumeratorWrapper.ItemCount;

        return this.UnboundItems.Count;
      }
    }

    public bool IsSynchronized
    {
      get
      {
        return false;
      }
    }

    public object SyncRoot
    {
      get
      {
        return this;
      }
    }

    #endregion

    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
      return this.CollectionViewSource.View.GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged Members

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void RaiseNotifyCollectionChanged( NotifyCollectionChangedEventArgs args )
    {
      if( args == null )
        throw new ArgumentNullException( "args" );

      if( this.CollectionChanged == null )
        return;

      this.CollectionChanged( this, args );
    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaiseNotifyPropertyChanged( string propertyName )
    {
      if( this.PropertyChanged == null )
        return;

      if( propertyName == null )
        propertyName = string.Empty;

      this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }

    #endregion
  }
}
